<?php

declare(strict_types=1);

namespace GSC;

use FastRoute\Dispatcher;
use RuntimeException;
use function FastRoute\simpleDispatcher;
use Swoole\Http\Request;
use Swoole\Http\Response;
use FastRoute\RouteCollector;

class Route
{
    private static $instance;

    private static $config;

    private static $dispatcher = null;

    private function __construct()
    {
    }

    public static function getInstance()
    {
        if (is_null(self::$instance)) {
            self::$instance   = new self();
            self::$config     = Config::getInstance()->get('routes', []);
            self::$dispatcher = simpleDispatcher(
                function (RouteCollector $routerCollector) {
                    foreach (self::$config as $routerDefine) {
                        $routerCollector->addRoute($routerDefine[0], $routerDefine[1], $routerDefine[2]);
                    }
                }
            );
        }
        return self::$instance;
    }


    public function dispatch(Request $request, Response $response)
    {
        $method    = $request->server['request_method'] ?? 'GET';
        $uri       = $request->server['request_uri'] ?? '/';
        $routeInfo = self::$dispatcher->dispatch($method, $uri);

        switch ($routeInfo[0]) {
            case Dispatcher::NOT_FOUND:
                return $this->defaultRouter($request, $response, $uri);
            case Dispatcher::METHOD_NOT_ALLOWED:
                $response->status(405);
                return $response->end();
            case Dispatcher::FOUND:
                try {
                    $handler = $routeInfo[1];
                    $vars    = $routeInfo[2];
                    if (is_string($handler)) {
                        $handler = explode('@', $handler);
                        if (count($handler) != 2) {
                            throw new RuntimeException("Route {$uri} config error, Only @ are supported");
                        }

                        $className = $handler[0];
                        $func      = $handler[1];

                        if (!class_exists($className)) {
                            throw new RuntimeException("Route {$uri} defined '{$className}' Class Not Found");
                        }

                        $controller = new $className();

                        if (!method_exists($controller, $func)) {
                            throw new RuntimeException("Route {$uri} defined '{$func}' Method Not Found");
                        }

                        $middlewareHandler = function ($request, $response, $vars) use ($controller, $func) {
                            return $controller->{$func}($request, $response, $vars ?? null);
                        };

                        $middleware = 'middleware';
                        if (property_exists($controller, $middleware)) {
                            $classMiddlewares  = $controller->{$middleware}['__construct'] ?? [];
                            $methodMiddlewares = $controller->{$middleware}[$func] ?? [];
                            $middlewares       = array_merge($classMiddlewares, $methodMiddlewares);
                            if ($middlewares) {
                                $middlewareHandler = $this->packMiddleware($middlewareHandler, array_reverse($middlewares));
                            }
                        }
                        return $middlewareHandler($request, $response, $vars ?? null);
                    }

                    if (is_callable($handler)) {
                        return call_user_func_array($handler, [$request, $response, $vars ?? null]);
                    }
                    throw new RuntimeException("Route {$uri} config error");
                } catch (\Throwable $throwable) {
                    return $this->exception($throwable, $request, $response);
                }
            default:
                $response->status(400);
                return $response->end();
        }
    }

    public function exception($throwable, Request $request, Response $response)
    {
        $exceptionClass = Config::getInstance()->get('exception.class', "");
        return new $exceptionClass($throwable, $request, $response);
    }


    public function defaultRouter(Request $request, Response $response, $uri)
    {
        $uri = trim($uri, '/');
        $uri = explode('/', $uri);

        if ($uri[0] === '') {
            $className = '\\App\\Controller\\' . CoreConst::DEFAULT_CONTROLLER;
            if (class_exists($className) && method_exists($className, CoreConst::DEFAULT_METHOD)) {
                return (new $className())->index($request, $response);
            }
        }
        $response->status(404);
        return $response->end();
    }

    /**
     * @param $handler
     * @param array $middlewares
     * @return mixed
     */
    public function packMiddleware($handler, array $middlewares = [])
    {
        foreach ($middlewares as $middleware) {
            $handler = $middleware($handler);
        }
        return $handler;
    }
}
